#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>

/*
 * GPBCON ==> 0x56000010
 * GPBDAT ==> 0x56000014
 * GPBUP  ==> 0x56000018
 */
#define GPIO_BASE_ADDR  0x56000000
#define GPBCON_OFFSET   0x10
#define GPBDAT_OFFSET   0x14
#define GPBUP_OFFSET    0x18

struct led_device_t {
    struct cdev dev;
    dev_t  no;
    unsigned int *io_gpbcon;
    unsigned int *io_gpbdat;
    unsigned int *io_gpbup;
};

struct led_device_t  leds[4];

ssize_t dev_write(struct file *fp, const char __user *buf, size_t count, loff_t *off)
{
    struct led_device_t *get; 
    unsigned int minor;

    get  = fp->private_data;
    minor = MINOR(get->no);

    printk("%d *con=0x%08x *dat=0x%08x\n", *(get->io_gpbcon), *(get->io_gpbdat));

    if ((*buf == 0) || (*buf == 48)) {
        printk("[%s %d ]\n", __FUNCTION__, __LINE__);
        *(get->io_gpbdat) &= ~(1 << (minor + 5));
    } else {
        printk("[%s %d ]\n", __FUNCTION__, __LINE__);
        *(get->io_gpbdat) |= (1 << (minor + 5));
    }

    printk("*con=0x%08x *dat=0x%08x *\n", *(get->io_gpbcon), *(get->io_gpbdat));

    return  4;
}

ssize_t dev_read(struct file *fp, char __user *buf, size_t count, loff_t *off)
{
    char str[32] = { 0 };

    struct led_device_t *get; 
    unsigned int minor;
    unsigned int data;

    get  = fp->private_data;
    minor = MINOR(get->no);
    data = *(get->io_gpbdat);

    printk("[%s %d ] data=0x%08x\n", __FUNCTION__, __LINE__, data);
    sprintf(str, "0x%08x", data);
    copy_to_user(buf, str, strlen(str));
                            
    return  4;
}


int dev_open(struct inode *no, struct file *fp)
{
    fp->private_data = container_of(no->i_cdev, struct led_device_t, dev);
    return 0;
}

int dev_release(struct inode *no, struct file *fp)
{
    printk("[%s %d ]\n", __FUNCTION__, __LINE__);
    return 0;
}

struct file_operations dev_ops = {
    .open = dev_open,
    .release = dev_release,
    .read = dev_read,
    .write = dev_write,
};

int dev_init(void)
{
    int i;   
    int ret = 0;
    char *virt;

    virt = ioremap(GPIO_BASE_ADDR, SZ_4K);

    for (i = 0; i < 4; i ++) {
        leds[i].no = MKDEV(52, i);
        cdev_init(&leds[i].dev, &dev_ops);
        leds[i].io_gpbcon = (unsigned int *)(virt + GPBCON_OFFSET);
        leds[i].io_gpbdat = (unsigned int *)(virt + GPBDAT_OFFSET);
        leds[i].io_gpbup  = (unsigned int *)(virt + GPBUP_OFFSET);
        /* set led gpio pin as output */
        *(leds[i].io_gpbcon) &= ~(1 << (i+5)*2+1);
    }

    ret = register_chrdev_region(leds[0].no, 4, "mycdevled");
    if (ret) {
        printk(" register device number failed.\n");
        return ret;
    }

    for (i = 0; i < 4; i ++) {
        cdev_add(&leds[i].dev, leds[i].no, 1);
    }

    return ret;
}

void dev_exit(void)
{
    int i;
    char * virt;

    virt = (char*)(leds[0].io_gpbcon);
    virt -= GPBCON_OFFSET;
    iounmap((void*)virt);

    for (i = 0; i < 4; i ++) {
        cdev_del(&leds[i].dev);
    }

    unregister_chrdev_region(leds[0].no, 4);   
    printk("[%s %d ]\n", __FUNCTION__, __LINE__);
}

module_init(dev_init);
module_exit(dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("chzhou");

// http://blogold.chinaunix.net/u4/120285/showart_2548258.html
